package be.android.forap.network;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.NoSuchProviderException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.bouncycastle.openpgp.PGPException;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.content.Entity;
import android.util.Log;
import android.widget.Toast;
import be.android.forap.ForAp;
import be.android.forap.crypto.PGPEncryption;
import be.android.forap.dataprovider.RawContact;

import com.dropbox.client2.DropboxAPI;
import com.dropbox.client2.DropboxAPI.DropboxFileInfo;
import com.dropbox.client2.DropboxAPI.DropboxLink;
import com.dropbox.client2.DropboxAPI.Entry;
import com.dropbox.client2.RESTUtility;
import com.dropbox.client2.android.AndroidAuthSession;
import com.dropbox.client2.exception.DropboxException;
import com.dropbox.client2.exception.DropboxUnlinkedException;
import com.dropbox.client2.session.AccessTokenPair;
import com.dropbox.client2.session.AppKeyPair;
import com.dropbox.client2.session.Session.AccessType;

final public class DropboxUtil {
	private static final String TAG = "DropboxUtil";	

	protected final static String APP_KEY = "jah1leslc916z6v";
	protected final static String APP_SECRET = "ngyy7mqrke3dx1o";
	protected final static AccessType ACCESS_TYPE = AccessType.APP_FOLDER;
	protected final static String FILE_NAME = "contacts.txt";

	private static DropboxAPI<AndroidAuthSession> mDBApi;

	protected static DropboxAPI<AndroidAuthSession> initializeSession(){
		AndroidAuthSession session = DropboxUtil.buildSession();
		return new DropboxAPI<AndroidAuthSession>(session);
	}

	private static AndroidAuthSession buildSession() {
		AppKeyPair appKeyPair = new AppKeyPair(APP_KEY, APP_SECRET);
		AndroidAuthSession session;

		String[] stored = getKeys();
		if (stored != null) {
			AccessTokenPair accessToken = new AccessTokenPair(stored[0], stored[1]);
			session = new AndroidAuthSession(appKeyPair, ACCESS_TYPE, accessToken);
		} else {
			session = new AndroidAuthSession(appKeyPair, ACCESS_TYPE);
		}
		return session;
	}	

	/**
	 * Shows keeping the access keys returned from Trusted Authenticator in a local
	 * store, rather than storing user name & password, and re-authenticating each
	 * time (which is not to be done, ever).
	 *
	 * @return Array of [access_key, access_secret], or null if none stored
	 */
	private static String[] getKeys() {
		String key = ForAp.getInstance().getAccesKeyName();
		String secret = ForAp.getInstance().getAccesSecretName();
		if (key != null && secret != null) {
			String[] ret = new String[2];
			ret[0] = key;
			ret[1] = secret;
			return ret;
		} else {
			return null;
		}
	}

	/**
	 * Shows keeping the access keys returned from Trusted Authenticator in a local
	 * store, rather than storing user name & password, and re-authenticating each
	 * time (which is not to be done, ever).
	 */
	protected static void storeKeys(String key, String secret) {		
		// Save the access key for later
		ForAp.getInstance().setAccesKeyNameAndSecretName(key,secret);		
	}

	protected static void clearKeys() {
		ForAp.getInstance().unlinkDropbox();
	}

	public static void logOutFormDropBox() {
		mDBApi = initializeSession();
		// Remove credentials from the session
		mDBApi.getSession().unlink();		
		// Clear our stored keys
		DropboxUtil.clearKeys();
	}

	/**
	 * Functions
	 */



	public static void uploadContentInFile(byte[] content, String fileName){
		mDBApi = DropboxUtil.initializeSession();
		ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
		try {
			//Toast.makeText(c, "link is "+ForAp.getInstance().isLinkedToDropbox(), Toast.LENGTH_LONG).show();
			Entry newEntry = mDBApi.putFileOverwrite("/"+fileName, inputStream, content.length, null);
			Log.i("DbExampleLog", "The uploaded file's rev is: " + newEntry.rev);
		} catch (DropboxUnlinkedException e) {
			// User has unlinked, ask them to link again here.
			Log.e("DbExampleLog", "User has unlinked.");
		} catch (DropboxException e) {
			Log.e("DbExampleLog", "Something went wrong while uploading.");
		}
	}

	private static boolean fileExists(String searchName) throws DropboxException{
		boolean b = false;
		mDBApi = DropboxUtil.initializeSession();
		try {
			//Toast.makeText(c, "link is "+ForAp.getInstance().isLinkedToDropbox(), Toast.LENGTH_LONG).show();
			List<Entry> newEntry = mDBApi.search("/", searchName, 1, false);
			Log.i(TAG, "The found file's rev is: " + newEntry.get(0).fileName() + " and equals " + searchName);
			if(searchName.equals(newEntry.get(0).fileName())){
				b = true;
			}			
		} catch (IndexOutOfBoundsException e) {
			Log.e(TAG, "no file found");
			b = false;
		} catch (DropboxUnlinkedException e) {
			// User has unlinked, ask them to link again here.
			Log.e(TAG, "User has unlinked.");
		}
		return b;
	}

	public static byte[] downloadFile(String fileName){
		mDBApi = DropboxUtil.initializeSession();
		try {
			ByteArrayOutputStream downloadStream = new ByteArrayOutputStream();
			DropboxFileInfo info = mDBApi.getFile("/"+fileName, null, downloadStream, null);
			Log.i("DbExampleLog", "The file's rev is: " + info.getMetadata().rev);
			//Log.i("DbExampleLog", "The file's contents are: " + new String(downloadStream.toByteArray()));
			return downloadStream.toByteArray();
		} catch (DropboxException e) {
			Log.e("DbExampleLog", "Something went wrong while downloading.");
		}
		return null;
	}



	public static String shareFile(String path) throws DropboxException {
		if(ForAp.getInstance().isOnline()){
			mDBApi = DropboxUtil.initializeSession();
			DropboxLink link = mDBApi.share(path);
			Entry existingEntry = mDBApi.metadata(path, 1, null, false, null);
			return link.url + "\n" + existingEntry.bytes;
		} else {
			//Toast.makeText(context, "No network detected", Toast.LENGTH_LONG).show();
			throw new DropboxException("No network");
		}
	}

	public static void uploadFile(byte[] fileContents, String fileName){

		mDBApi = DropboxUtil.initializeSession();

		// Uploading content.
		ByteArrayInputStream inputStream = new ByteArrayInputStream(fileContents);
		try {
			Entry newEntry = mDBApi.putFileOverwrite("/" + fileName , inputStream, fileContents.length, null);
			Log.i("DbExampleLog", "The uploaded file's rev is: " + newEntry.rev);
		} catch (DropboxUnlinkedException e) {
			// User has unlinked, ask them to link again here.
			Log.e("DbExampleLog", "User has unlinked.");
		} catch (DropboxException e) {
			Log.e("DbExampleLog", "Something went wrong while uploading.");
		}	
	}

	private static long localSyncState;

	public static long getLocalSyncState() {
		return localSyncState;
	}
	
	public static long getNewServerSyncState() throws DropboxException {

		mDBApi = DropboxUtil.initializeSession();

		long serverSyncState;
		if(fileExists(FILE_NAME)){
			//Date of contacts file
			Log.i(TAG,"*** file exists");
			Entry contactsEntry = mDBApi.metadata("/"+FILE_NAME, 1, null, false, null);
			String modifiedDate = contactsEntry.modified;
			Date serverDate = RESTUtility.parseDate(modifiedDate);
			serverSyncState = serverDate.getTime();	
		} else {
			serverSyncState = 0;
		}
		return serverSyncState;
	}

	public static List<RawContact> getServerContacts() throws JSONException, DropboxException, NoSuchProviderException, IOException {
		mDBApi = DropboxUtil.initializeSession();

		if(fileExists(FILE_NAME)){
			//DOWNLOAD CONTACTS			
			DropboxFileInfo downloadInfo;
			ByteArrayOutputStream downloadStream = new ByteArrayOutputStream();
			downloadInfo = downloadContacts(downloadStream);
			final String downloadContents = new String(downloadStream.toByteArray());

			JSONArray serverJsonContacts = new JSONArray(downloadContents);
			ArrayList<RawContact> serverContacts = new ArrayList<RawContact>();
			for (int i = 0; i < serverJsonContacts.length(); i++) {
				RawContact rawContact = RawContact.valueOf(serverJsonContacts.getJSONObject(i));
				if (rawContact != null) {
					serverContacts.add(rawContact);
				}
			}
			localSyncState = RESTUtility.parseDate(downloadInfo.getMetadata().modified).getTime();
			return serverContacts;
		} else {
			return null;
		}
	}

	/**
	 * Perform 2-way sync with the server-side contacts. We send a request that
	 * includes all the locally-dirty contacts so that the server can process
	 * those changes, and we receive (and return) a list of contacts that were
	 * updated on the server-side that need to be updated locally.
	 *
	 * @param account The account being synced
	 * @param authtoken The authtoken stored in the AccountManager for this
	 *            account
	 * @param serverSyncState A token returned from the server on the last sync
	 * @param dirtyContacts A list of the contacts to send to the server
	 * @return 
	 * @return A list of contacts that we need to update locally
	 * @throws JSONException 
	 * @throws DropboxException 
	 * @throws PGPException 
	 * @throws IOException 
	 * @throws NoSuchProviderException 
	 */
	public static void syncContacts(List<RawContact> dirtyLocalContacts) 
			throws JSONException, DropboxException, NoSuchProviderException, IOException, PGPException {

		mDBApi = DropboxUtil.initializeSession();

		if(fileExists(FILE_NAME) && !dirtyLocalContacts.isEmpty()){
			//DOWNLOAD CONTACTS
			ByteArrayOutputStream downloadStream = new ByteArrayOutputStream();
			downloadContacts(downloadStream);
			final String downloadContents = new String(downloadStream.toByteArray());

			JSONArray serverJsonContacts = new JSONArray(downloadContents);
			ArrayList<RawContact> serverContacts = new ArrayList<RawContact>();
			for (int i = 0; i < serverJsonContacts.length(); i++) {
				RawContact rawContact = RawContact.valueOf(serverJsonContacts.getJSONObject(i));
				if (rawContact != null) {
					serverContacts.add(rawContact);
					Log.i("SYNC","Dwonloaded: "+rawContact.getBestName());
				}
			}
			//SYNC CONTACTS DROPBOX FILE
			List<RawContact> updatedList = updateContactFile(dirtyLocalContacts, serverContacts);
			//UPLOAD CONTACTS
			Entry entry = uploadContacts(updatedList);

			localSyncState = RESTUtility.parseDate(entry.modified).getTime();
		}else{	
			ArrayList<RawContact> uploadContacts = new ArrayList<RawContact>();
			for (RawContact rc:dirtyLocalContacts){
				if(!rc.isDeleted()){
					uploadContacts.add(rc);
				}
			}
			Entry entry = uploadContacts(uploadContacts);
			localSyncState = RESTUtility.parseDate(entry.modified).getTime();
		}
	}

	private static List<RawContact> updateContactFile(List<RawContact> dirtylocalContacts, ArrayList<RawContact> serverContacts) {
		ArrayList<RawContact> updatedContacts = new ArrayList<RawContact>();
		updatedContacts.addAll(serverContacts);
		updatedContacts.removeAll(dirtylocalContacts);
		updatedContacts.addAll(dirtylocalContacts);

		//Because of concurrentmodification error
		ArrayList<RawContact> removableContacts = new ArrayList<RawContact>();
		for(RawContact contact:updatedContacts){
			Log.i("SYNC","update list has: "+contact.getGlobalContactId());
			Log.i("SYNC","update list has: "+contact.isDeleted());
			if(contact.isDeleted()){
				Log.i("SYNC","DELETED contact removed from update list: "+contact.getBestName());
				removableContacts.add(contact);
			}
			Log.i("SYNC","update list has: "+updatedContacts.contains(contact));
		}
		updatedContacts.removeAll(removableContacts);

		return updatedContacts;
	}

	private static DropboxFileInfo downloadContacts(ByteArrayOutputStream decryptedStream) throws DropboxException, NoSuchProviderException, IOException{
		ByteArrayOutputStream downloadStream = new ByteArrayOutputStream();
		DropboxFileInfo info = mDBApi.getFile("/"+FILE_NAME, null, downloadStream, null);
		ForAp app  = ForAp.getInstance();
		byte[] decrypted = PGPEncryption.decryptWithPGPKey(downloadStream.toByteArray(), app.getPrivateKey().getBytes(), app.getPassword().toCharArray());
		decryptedStream.write(decrypted);
		return info;
	}

	private static Entry uploadContacts(List<RawContact> newList) throws DropboxException, NoSuchProviderException, IOException, PGPException{
		//JSONArray serverJsonContacts = new JSONArray(newList);		
		//String contents = serverJsonContacts.toString();
		List<JSONObject> ul = new ArrayList<JSONObject>();
		for(RawContact rc : newList){
			JSONObject upload = rc.toJSONObject();
			ul.add(upload);
		}		
		// Uploading content.
		String contents = ul.toString();
		Log.i("DbExampleLog", "The uploaded file's rev is: " + contents);
		
		//encrypt OpenPGP with pubkey of user
		byte[] encrypted = PGPEncryption.encryptWithPGPKey(contents.getBytes(), ForAp.getInstance().getPublicKey().getBytes(), true, true);
		
		ByteArrayInputStream inputStream = new ByteArrayInputStream(encrypted);
		
		Entry newEntry = mDBApi.putFileOverwrite("/" + FILE_NAME, inputStream, encrypted.length, null);
		Log.i("DbExampleLog", "The uploaded file's rev is: " + newEntry.rev);
		return newEntry;
	}

	/*protected final static String FILE_NAME_TEST = "/contactstest.txt";


	public static DropboxFileInfo downloadContactsTEST(ByteArrayOutputStream downloadStream) throws DropboxException{
		mDBApi = initializeSession();
		//try {
		DropboxFileInfo info = mDBApi.getFile(DropboxUtil.FILE_NAME_TEST, null, downloadStream, null);
		Log.i("DbExampleLog", "The file's rev is: " + info.getMetadata().rev);
		Log.i("DbExampleLog", "The file's contents are: " + new String(downloadStream.toByteArray()));
		return info;
		/*} catch (DropboxException e) {
			Log.e("DbExampleLog", "Something went wrong while downloading.");
		}
		return null;*/
	/*}

	public static Entry uploadContactsTEST(List<RawContact> newList) throws DropboxException{
		mDBApi = initializeSession();
		//JSONArray serverJsonContacts = new JSONArray(newList);		
		//String contents = serverJsonContacts.toString();
		List<JSONObject> ul = new ArrayList<JSONObject>();
		for(RawContact rc : newList){
			JSONObject upload = rc.toJSONObject();
			ul.add(upload);
		}		
		// Uploading content.
		String contents = ul.toString();

		ByteArrayInputStream inputStream = new ByteArrayInputStream(contents.getBytes());
		Entry newEntry = mDBApi.putFileOverwrite(DropboxUtil.FILE_NAME_TEST, inputStream, contents.length(), null);
		Log.i("DbExampleLog", "The uploaded file's rev is: " + newEntry.rev);
		return newEntry;
	}*/

	public static void deleteContactsFile() throws DropboxException{
		mDBApi = initializeSession();
		if(fileExists(FILE_NAME))
			mDBApi.delete(FILE_NAME);
	}

	/*public static void uploadDirtyContacts(RawContact dirtyContact,Context c){
		mDBApi = DropboxUtil.initializeSession();


		JSONObject upload = dirtyContact.toJSONObject();
		List<JSONObject> ul = new ArrayList<JSONObject>();
		ul.add(upload);
		// Uploading content.
		String fileContents = ul.toString();
		ByteArrayInputStream inputStream = new ByteArrayInputStream(fileContents.getBytes());
		try {
			Entry newEntry = mDBApi.putFile(DropboxUtil.FILE_NAME, inputStream, fileContents.length(), null, null);
			Log.i("DbExampleLog", "The uploaded file's rev is: " + newEntry.rev);
		} catch (DropboxUnlinkedException e) {
			// User has unlinked, ask them to link again here.
			Log.e("DbExampleLog", "User has unlinked.");
		} catch (DropboxException e) {
			Log.e("DbExampleLog", "Something went wrong while uploading.");
		}
	}*/
	
	private static String smsFolder = "SMS";
	private static int smsLimit = 50;
	
	public static String uploadSMS(byte[] content) throws DropboxException{
		mDBApi = initializeSession();
		/*if(!fileExists(smsFolder)){
			mDBApi.createFolder(smsFolder);
		}*/
		String fileName = "error.txt.enc";
		Log.d("d",fileName);
		Entry metaData = mDBApi.metadata("/"+smsFolder, smsLimit,null, true, null);
		Log.d("d",metaData.path);
		List<Entry> childrenMetaData = metaData.contents;
		if(childrenMetaData.size() == 50){
			Date oldestSMS = RESTUtility.parseDate(childrenMetaData.get(0).modified);
			fileName = childrenMetaData.get(0).fileName();
			
			for(Entry e:childrenMetaData){
				Date eDate = RESTUtility.parseDate(e.modified);
				if(eDate.before(oldestSMS)){
					oldestSMS = eDate;
					fileName = e.fileName();
				}
			}		
		}else{
			fileName = "sms"+(childrenMetaData.size())+".txt.enc";
		}
		Log.d("d",fileName);
		ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
		Entry newEntry = mDBApi.putFileOverwrite("/" + smsFolder + "/" + fileName , inputStream, content.length, null);
		
		return shareFile(newEntry.path);
	}

}
